scan-thread-pool 技术文档该项目是用于扫描文件系统的 Node.js 工具,其主要目标是高效地查找并计算指定目录下所有 node_modules 文件夹的大小。项目利用多线程技术来并发执行扫描任务,从而显著提高在大型项目或磁盘上的扫描性能
worker_threads 模块,将文件扫描任务分配到多个线程中,避免阻塞主线程Subject 来处理和传递扫描结果,实现了模块间的解耦get-folder-size 库,用于准确计算目录的磁盘占用空间整体架构图和技术栈说明:
+-----------------+ +-----------------+
| Main Thread |----->| ScanService |
| (index.ts) | | (scan.ts) |
+-----------------+ +-----------------+
^ |
| | 1. Distributes tasks
| v
+-----------------+ +-----------------+
| RxJS Subject |<-----| Worker Thread |
| (Data Stream) | | (scan.worker.ts)|
+-----------------+ +-----------------+
|
| 2. Scans filesystem
v
+-----------------+
| FileWalker |
| (file-walker.ts)|
+-----------------+各组件/模块的职责划分:
index.ts (主线程): 应用程序的入口,负责初始化 ScanService,创建 RxJS Subject,并订阅最终的扫描结果ScanService (扫描服务): 核心协调器,负责创建和管理工作线程池,通过 MessageChannel 与工作线程通信,并使用轮询算法分发扫描任务scan.worker.ts (工作线程): 在独立的线程中执行文件扫描任务。每个工作线程包含一个 FileWalker 实例,并通过 MessagePort 与主线程进行通信FileWalker (文件遍历器): 封装了文件系统遍历的底层逻辑,使用 fs.promises.opendir 异步读取目录内容,并通过 EventEmitter 发出扫描结果数据流向和接口设计:
index.ts 启动 ScanService,ScanService 根据 CPU 核心数创建相应数量的工作线程,并为每个线程建立一个 MessageChannelScanService 将初始扫描路径作为一个任务,通过轮询算法选择一个工作线程,并通过 MessagePort 将任务发送出去FileWalker 开始遍历文件系统。FileWalker 采用广度优先策略,将发现的子目录作为新的结果发出FileWalker 的 newResult 事件后,将结果通过 MessagePort 回传给 ScanServiceScanService 接收到结果后,判断路径是否为 node_modules。如果是,则通过 RxJS Subject 将路径推送到最终结果流;如果不是,则将其作为新的扫描任务,重新分发给工作线程index.ts 中的订阅者接收到 node_modules 路径后,调用 getSize() 计算其大小并输出到控制台核心功能的技术实现细节:
ScanService 的 initWorkers 方法利用 os.cpus().length 来动态创建与 CPU 核心数相等的工作线程,以最大化并行处理能力。MessageChannel 进行通信。通过在 postMessage 时传递 [port2],MessagePort 的所有权被转移,避免了数据序列化和反序列化的开销。FileWalker 使用 fs.promises.opendir 和 dir.read() 进行全异步的目录读取,确保了高吞吐量和非阻塞的执行。关键算法或业务流程说明:
ScanService 中的 addJob 方法通过一个简单的索引递增和取模操作,实现了轮询调度,确保了扫描任务在工作线程之间均匀分布。ScanService 类:
startScan(stream$: Subject<string>, path: string): 启动扫描过程
stream$: 用于接收 node_modules 路径的 RxJS Subjectpath: 初始扫描目录FileWalker 类:
enqueueTask(path: string): 将一个目录路径加入到内部任务队列以供处理events (EventEmitter):
newResult 事件: 当一个目录被扫描完成时触发,负载为 { results: Array<{ path: string; isTarget: boolean }> }